package com.wyl.neo4j.util;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import org.neo4j.driver.*;
import org.neo4j.driver.internal.InternalRelationship;
import org.neo4j.driver.summary.ResultSummary;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;

/**
 * @author 吴宇亮 on 2022/8/3 9:26
 */
@Component
public class Neo4jUtil implements ApplicationContextAware, InitializingBean {

    private static ApplicationContext applicationContext;
    private static Neo4jClient neo4jClient;
    private static Driver driver;
    private static DatabaseSelectionProvider databaseSelectionProvider;

    public static void getLinkById(int linkId) {
        //        MATCH ()-[r]-() WHERE ID(r)=6 RETURN r
        String cql = "MATCH ()-[r]-() WHERE ID(r)=" + linkId + " RETURN r";
        ResultSummary title = neo4jClient.query(cql)
                .in(database())
                .bindAll(Map.of("title", ""))
                .run();

        List<Record> records = readCql(cql);
        System.out.println();
    }

    public static void deleteAllNodesAndLinks() {
//        MATCH (n) DETACH DELETE n
        Neo4jUtil.runCql("MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r");
    }

    public static void deleteLinkById(Long id) {
        Neo4jUtil.runCql("MATCH ()-[r]-() WHERE ID(r)=" + id + " DELETE r RETURN r");
    }

    public static void deleteLinkById(Object startNode, Object endNode, String linkTag) {
        String startNodeLabel = Neo4jUtil.getNodeLabel(startNode);
        String endNodeLabel = Neo4jUtil.getNodeLabel(endNode);

        String startNodeJson = Neo4jUtil.getNodeJson(startNode);
        String endNodeJson = Neo4jUtil.getNodeJson(endNode);

        String cql = "MATCH (start:" + startNodeLabel + startNodeJson + ") -[r:" + linkTag + "]-> (end:"+ endNodeLabel + endNodeJson + ") delete r";
        Neo4jUtil.runCql(cql);
    }

    private static String getNodeLabel(Object node) {
        String[] startNodeLabels = node.getClass().getAnnotation(Node.class).labels();
        return startNodeLabels[0];
    }

    private static String getNodeJson(Object node) {
        Map<String, Object> propertyMap = BeanUtil.beanToMap(node, false, true);
        if(CollUtil.isEmpty(propertyMap)){
            return "";
        }
        StringBuilder json = new StringBuilder();
        boolean isFirst = true;
        for (String key : propertyMap.keySet()) {
            Object value = propertyMap.get(key);
            if(isFirst){
                isFirst = false;
            } else {
                json.append(",");
            }
            json.append(key).append(":\"").append(value).append("\"");
        }

        json.insert(0, "{");
        json.append("}");
        return json.toString();
    }

    /**
     * @param condition 要过滤的条件
     * @param <T>       条件的类型
     * @return 根据bean去构造neo4j-data的条件
     * <p>
     * 参考：
     * Spring Data JPA 之 QueryByExampleExecutor 的用法和原理分析
     * https://blog.csdn.net/qq_40161813/article/details/125581497?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165949848016782184699235%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=165949848016782184699235&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~pc_rank_34-5-125581497-null-null.142^v39^pc_rank_34_ecpm0&utm_term=QueryByExampleExecutor&spm=1018.2226.3001.4187
     */
    public static <T> Example<T> getExample(T condition) {
        Class<?> clazz = condition.getClass();
        Map<String, Object> propertyMap = BeanUtil.beanToMap(condition, false, true);
        ExampleMatcher exampleMatcher = ExampleMatcher.matching();
        for (String key : propertyMap.keySet()) {
            Field field = ReflectUtil.getField(clazz, key);
            if (String.class == field.getType()) {
                exampleMatcher.withMatcher("name", ExampleMatcher.GenericPropertyMatchers.contains());
            } else {
                exampleMatcher.withMatcher("name", ExampleMatcher.GenericPropertyMatchers.exact());
            }
        }
        return Example.of(condition, exampleMatcher);
    }

    public static List<Record> readCql(String cql) {
        try (Session session = getSession(database())) {
            System.out.println(cql);
            return session.readTransaction(tx -> tx.run(cql).list());
        }
    }

    /**
     * 执行自定义的cql
     */
    public static void runCql(String cql) {
        try (Session session = getSession(database())) {
            System.out.println(cql);
            session.run(cql);
        }
    }

    public static Session getSession() {
        return getSession(null);
    }

    private static Session getSession(String database) {
        if (StrUtil.isBlank(database)) {
            return driver.session();
        }
        return driver.session(SessionConfig.forDatabase(database));
    }

    private static String database() {
        return databaseSelectionProvider.getDatabaseSelection().getValue();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Neo4jUtil.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Neo4jUtil.driver = applicationContext.getBean(Driver.class);
        Neo4jUtil.databaseSelectionProvider = applicationContext.getBean(DatabaseSelectionProvider.class);
        Neo4jUtil.neo4jClient = applicationContext.getBean(Neo4jClient.class);
    }


}
